package ping6;

use Inline C;

BEGIN {
	our ($VERSION, @ISA, @EXPORT, @EXPORT_OK);

	$VERSION=0.6;
	@ISA	= qw(Exporter);
	@EXPORT	= qw(&ping6);
}

our @EXPORT_OK;
sub ping6($$)  {
my $h=shift;
my $t=shift;

	$t*=1000000;
	return c_ping6($h,$t);
}
1;

__DATA__

__C__
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/time.h>

#include <netinet/in.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>				/* offsetof */

static const int DEFDATALEN = 56;
static const int MAXIPLEN = 60;
static const int MAXICMPLEN = 76;
static const int MAXPACKET = 65468;
#define	MAX_DUP_CHK	(8 * 128)
static const int MAXWAIT = 10;
static const int PINGINTERVAL = 1;		/* second */

static int _rc = 0;

#define	A(bit)		rcvd_tbl[(bit)>>3]	/* identify byte in array */
#define	B(bit)		(1 << ((bit) & 0x07))	/* identify bit in byte */
#define	SET(bit)	(A(bit) |= B(bit))
#define	CLR(bit)	(A(bit) &= (~B(bit)))
#define	TST(bit)	(A(bit) & B(bit))

/* static int ping(const char *host); */
static struct hostent *h;

int _create_icmp6_socket(void)
{
	int sock;
	sock = socket(AF_INET6, SOCK_RAW|SOCK_NONBLOCK, IPPROTO_ICMPV6);
	if (sock < 0) {
		if (errno == EPERM) {
			/* fprintf(stderr,"Permissions denied - are you root?\n"); */
			return -1;
		}
		/* perror("Can not create raw socket"); */
		return -1;
	}

	/* drop root privs if running setuid */
	setuid(getuid());
	return sock;
}

/* Timeout in microseconds (1,000,000 == 1 second) */

int c_ping6(const char *host, long timeout)
{
	_rc = 99;
	struct sockaddr_in6 pingaddr;
	struct icmp6_hdr *pkt;
	int pingsock, c;
	int sockopt;
	char packet[DEFDATALEN + MAXIPLEN + MAXICMPLEN];

	pingsock = _create_icmp6_socket();
	if(pingsock==-1) {
		_rc=-1;
		return -1;
	}

	memset(&pingaddr, 0, sizeof(struct sockaddr_in));

	pingaddr.sin6_family = AF_INET6;
	h = gethostbyname2(host, AF_INET6);
	if(!h) {
		close(pingsock);
		_rc=-2;
		return -2;
	}
	memcpy(&pingaddr.sin6_addr, h->h_addr, sizeof(pingaddr.sin6_addr));

	pkt = (struct icmp6_hdr *) packet;
	memset(pkt, 0, sizeof(packet));
	pkt->icmp6_type = ICMP6_ECHO_REQUEST;

	sockopt = offsetof(struct icmp6_hdr, icmp6_cksum);
	setsockopt(pingsock, SOL_RAW, IPV6_CHECKSUM, (char *) &sockopt,
			   sizeof(sockopt));

	c = sendto(pingsock, packet, sizeof(packet), 0,
			   (struct sockaddr *) &pingaddr, sizeof(struct sockaddr_in6));

	if (c < 0 || c != sizeof(packet)) {
		close(pingsock);
		_rc=-1;
		return -1;
	}

	/* Get current time... */
	struct timeval start_tv;
	struct timeval end_tv;
	int rc=gettimeofday(&start_tv,NULL);
	/* listen for replies */
	while (1) {
		long sdiff2;
		struct sockaddr_in6 from;
		socklen_t fromlen = sizeof(from);

		if(_rc != 99)
			break;
	
		rc=gettimeofday(&end_tv,NULL);
		time_t sdiff=end_tv.tv_sec - start_tv.tv_sec;
		sdiff=sdiff * 1000000; /* convert to microseconds */
		
		if(sdiff>0) {
			sdiff=sdiff - start_tv.tv_usec + end_tv.tv_usec;
		} else {
			sdiff2=abs((long)(end_tv.tv_usec - start_tv.tv_usec));
			sdiff=sdiff2;
		}
		if(sdiff > timeout) {
			_rc=-2;
			break;
		}

		if ((c = recvfrom(pingsock, packet, sizeof(packet), 0,
						  (struct sockaddr *) &from, &fromlen)) < 0) {
			if(errno==EAGAIN || errno==EWOULDBLOCK) {
				usleep(1000);
			}
			continue;
		}
		if (c >= 8) {			/* icmp6_hdr */
			pkt = (struct icmp6_hdr *) packet;
			if (pkt->icmp6_type == ICMP6_ECHO_REPLY) {
				if(!memcmp(&pingaddr.sin6_addr,&from.sin6_addr,sizeof(pingaddr.sin6_addr))) {
					_rc=1;
					break;
				}
			}
		}
	}
	close(pingsock);
	return _rc;
}
/*

extern int main(int argc, char **argv)
{
	argc--;
	argv++;
	if (argc < 1)
		exit(1);
	ping(argv[0],1000000L);
	printf("RC=%d\n",_rc);
	exit(_rc != 0);
}

*/
